package org.lee.app.mediator;

import lombok.extern.slf4j.Slf4j;
import org.lee.common.util.DateUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.*;

/**
 * DefaultSqlSession
 * 默认会话
 *
 * @author dralee
 * @date 2021-11-23 15:22:16
 **/
@Slf4j
public class DefaultSqlSession implements SqlSession {

    private Connection connection;
    private Map<String, XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement) {
        return selectOne(statement, null);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        XNode node = mapperElement.get(statement);
        try {
            PreparedStatement ps = connection.prepareStatement(node.getSql());
            if (parameter != null) {
                Map<Integer, String> parameterMap = node.getParameter();
                buildParameter(ps, parameter, parameterMap);
            }
            ResultSet rs = ps.executeQuery();
            List<T> list = resultSet2Object(rs, Class.forName(node.getResultType()));
            return list.get(0);
        } catch (SQLException | ClassNotFoundException | IllegalAccessException throwables) {
            log.error("selectOne error: {}", throwables);
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement) {
        return selectList(statement, null);
    }

    @Override
    public <T> List<T> selectList(String statement, Object parameter) {
        XNode node = mapperElement.get(statement);
        try {
            PreparedStatement ps = connection.prepareStatement(node.getSql());
            if (parameter != null) {
                Map<Integer, String> parameterMap = node.getParameter();
                buildParameter(ps, parameter, parameterMap);
            }
            ResultSet rs = ps.executeQuery();
            List<T> list = resultSet2Object(rs, Class.forName(node.getResultType()));
            return list;
        } catch (SQLException | ClassNotFoundException | IllegalAccessException throwables) {
            log.error("selectOne error: {}", throwables);
        }
        return null;
    }

    private void buildParameter(PreparedStatement ps, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
        int size = parameterMap.size();
        // 单值
        if (parameter instanceof Short) {
            for (int i = 1; i <= size; ++i) {
                ps.setShort(i, Short.parseShort(parameter.toString()));
            }
        }
        if (parameter instanceof Long) {
            for (int i = 1; i <= size; ++i) {
                ps.setLong(i, Long.parseLong(parameter.toString()));
            }
            return;
        }
        if (parameter instanceof Integer) {
            for (int i = 1; i <= size; ++i) {
                ps.setLong(i, Integer.parseInt(parameter.toString()));
            }
            return;
        }
        if (parameter instanceof String) {
            for (int i = 1; i <= size; ++i) {
                ps.setString(i, parameter.toString());
            }
            return;
        }
        if (parameter instanceof Date) {
            //long time = ((Date) parameter).getTime();
            for (int i = 1; i <= size; ++i) {
                ps.setDate(i, (java.sql.Date) parameter);
            }
            return;
        }
        // 对象
        Map<String, Object> fieldMap = new HashMap<>();
        for (Field field : parameter.getClass().getDeclaredFields()) {
            String name = field.getName();
            field.setAccessible(true);
            Object obj = field.get(parameter);
            field.setAccessible(false);
            fieldMap.put(name, obj);
        }
        for (int i = 1; i <= size; ++i) {
            Object obj = fieldMap.get(parameterMap.get(i));
            if (obj instanceof Short) {
                ps.setShort(i, Short.parseShort(obj.toString()));
            }
            if (obj instanceof Integer) {
                ps.setInt(i, Integer.parseInt(obj.toString()));
            }
            if (obj instanceof Long) {
                ps.setLong(i, Long.parseLong(obj.toString()));
            }
            if (obj instanceof String) {
                ps.setString(i, obj.toString());
            }
            if (obj instanceof Date) {
                ps.setDate(i, (java.sql.Date) obj);
            }
        }
    }

    private <T> List<T> resultSet2Object(ResultSet rs, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = rs.getMetaData();
            int count = metaData.getColumnCount();
            while (rs.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= count; ++i) {
                    Object val = rs.getObject(i);
                    String name = metaData.getColumnName(i);
                    String setMethod = "set".concat(name.substring(0, 1).toUpperCase()).concat(name.substring(1));
                    Method method;
                    if (val instanceof Timestamp) {
                        val = new Date(((Timestamp) val).getTime());
                        method = clazz.getMethod(setMethod, Date.class);
                    } else if (val instanceof LocalDateTime || val instanceof LocalDate || val instanceof LocalTime) {
                        val = DateUtil.fromLocalDateTime((LocalDateTime) val);
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, val.getClass());
                    }
                    method.invoke(obj, val);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            log.error("resultSet2Object error: {}", e);
        }
        return list;
    }

    @Override
    public void close() {
        if (connection == null) {
            return;
        }
        try {
            connection.close();
        } catch (SQLException e) {
            log.error("close error: {}", e);
        }
    }
}
